package com.chen.recruit.common.aspect;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.chen.recruit.common.annotation.Log;
import com.chen.recruit.common.api.CommonResult;
import com.chen.recruit.common.api.ResultCode;
import com.chen.recruit.common.constants.MessageConstants;
import com.chen.recruit.common.constants.SysConstants;
import com.chen.recruit.common.enums.BusinessStatus;
import com.chen.recruit.common.utils.AddressUtil;
import com.chen.recruit.common.utils.SysUtils;
import com.chen.recruit.domain.LoginLog;
import com.chen.recruit.domain.OperateLog;
import com.chen.recruit.domain.vo.LoginVo;
import com.chen.recruit.mapper.LoginLogMapper;
import com.chen.recruit.mapper.OperateLogMapper;
import eu.bitwalker.useragentutils.UserAgent;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;

/**
 * 统一日志切面
 *
 * @author cgy
 * @date 2022/09/04
 */
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class LogAspect {

    private final LoginLogMapper loginLogMapper;
    private final OperateLogMapper operateLogMapper;

    /**
     * 登录日志
     */
    @AfterReturning(returning = "result", pointcut = "execution(public * com.chen.recruit.controller.UserController.login(..))")
    public void loginLog(JoinPoint joinPoint, CommonResult result) {
        HttpServletRequest request = getRequest();

        LoginLog loginLog = new LoginLog();
        loginLog.setLoginTime(new Date());
        loginLog.setMsg(
                result.getMessage().equals(ResultCode.SUCCESS.getMessage()) ?
                        MessageConstants.LOGIN_SUCCESS : result.getMessage()
        );
        loginLog.setUsername(((LoginVo) joinPoint.getArgs()[0]).getUsername());

        String ip = ServletUtil.getClientIP(request);
        loginLog.setIp(ip);
        loginLog.setLocation(AddressUtil.getRealAddressByIP(ip));

        UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader(SysConstants.UA));
        loginLog.setBrowser(userAgent.getBrowser().getName());
        loginLog.setOs(userAgent.getOperatingSystem().getName());

        loginLog.setStatus(
                result.getCode() == ResultCode.SUCCESS.getCode() ? BusinessStatus.SUCCESS : BusinessStatus.FAIL
        );

        loginLogMapper.insert(loginLog);
    }

    /**
     * 记录操作（有返回结果）
     */
    @AfterReturning(value = "@annotation(controllerLog)", returning = "result")
    public void operationLog(JoinPoint joinPoint, Log controllerLog, CommonResult result) {
        handleLog(joinPoint, controllerLog, null, result);
    }

    /**
     * 记录操作（无返回结果）
     */
    @AfterReturning("@annotation(controllerLog)")
    public void operationLog(JoinPoint joinPoint, Log controllerLog) {
        handleLog(joinPoint, controllerLog, null, null);
    }

    /**
     * 拦截异常操作
     */
    @AfterThrowing(value = "@annotation(controllerLog)", throwing = "exception")
    public void saveExceptionOperationLog(JoinPoint joinPoint, Log controllerLog, Exception exception) {
        handleLog(joinPoint, controllerLog, exception, null);
    }

    /**
     * 记录日志
     */
    private void handleLog(JoinPoint joinPoint, Log controllerLog, Exception exception, CommonResult result) {
        try {
            HttpServletRequest request = getRequest();

            OperateLog operateLog = new OperateLog();

            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            operateLog.setModule(controllerLog.module());
            operateLog.setBusinessType(controllerLog.businessType());

            String className = joinPoint.getTarget().getClass().getName();
            String methodName = method.getName();
            methodName = className + StrUtil.DOT + methodName;
            operateLog.setMethod(methodName);
            operateLog.setReqMethod(request.getMethod());

            Map<String, String> rtnMap = convertMap(request.getParameterMap());
            operateLog.setReqParam(CollectionUtils.isEmpty(rtnMap) ?
                    getParameter(method, joinPoint.getArgs()) : JSON.toJSONString(rtnMap));

            if (ObjectUtil.isNotNull(result)) {
                operateLog.setRespParam(JSON.toJSONString(result));
                operateLog.setStatus(
                        result.getCode() == ResultCode.SUCCESS.getCode() ? BusinessStatus.SUCCESS : BusinessStatus.FAIL
                );
            } else {
                operateLog.setStatus(BusinessStatus.SUCCESS);
            }
            if (ObjectUtil.isNotNull(exception)) {
                operateLog.setStatus(BusinessStatus.FAIL);
            }

            operateLog.setOperator(SysUtils.getLoginUser().getUsername());

            String ip = ServletUtil.getClientIP(request);
            operateLog.setIp(ip);
            operateLog.setLocation(AddressUtil.getRealAddressByIP(ip));

            operateLog.setReqUrl(request.getRequestURI());
            operateLog.setOperateTime(new Date());

            operateLogMapper.insert(operateLog);
        } catch (Exception e) {
            log.error("==操作异常==");
            log.error("异常信息:{}", e.getMessage());
            e.printStackTrace();
        }

    }

    private HttpServletRequest getRequest() {
        return (HttpServletRequest) Objects
                .requireNonNull(RequestContextHolder.getRequestAttributes())
                .resolveReference(RequestAttributes.REFERENCE_REQUEST);
    }

    /**
     * 转换request 请求参数
     */
    private Map<String, String> convertMap(Map<String, String[]> paramMap) {
        Map<String, String> rtnMap = new HashMap<>();
        for (String key : paramMap.keySet()) {
            rtnMap.put(key, paramMap.get(key)[0]);
        }
        return rtnMap;
    }

    /**
     * 获取请求参数（JSON格式）
     */
    private String getParameter(Method method, Object[] args) {
        List<Object> argList = new ArrayList<>();
        Parameter[] parameters = method.getParameters();
        // 从方法参数列表中筛选请求参数
        for (int i = 0; i < parameters.length; i++) {
            // 将RequestBody注解修饰的参数作为请求参数
            RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class);
            if (requestBody != null) {
                argList.add(args[i]);
            }
            // 将PathVariable注解修饰的参数作为请求参数
            PathVariable pathVariable = parameters[i].getAnnotation(PathVariable.class);
            if (pathVariable != null) {
                Map<String, Object> map = new HashMap<>();
                String key = StringUtils.isEmpty(pathVariable.value()) ?
                        parameters[i].getName() : pathVariable.value();
                map.put(key, args[i]);
                argList.add(map);
            }
        }
        return CollUtil.isEmpty(argList) ? null : JSONUtil.parse(argList).toString();
    }

}
